Skip to content

fix(spec): defineStack cross-reference validation reaches inline page-element actions (#6889) - #7392

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-6889-inline-action-crossref
Aug 10, 2026
Merged

fix(spec): defineStack cross-reference validation reaches inline page-element actions (#6889)#7392
os-zhuang merged 1 commit into
mainfrom
claude/issue-6889-inline-action-crossref

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #6889

defineStack's action cross-reference walk iterated config.actions — the registered
action list — only. An action authored inline on a page element (element:button
properties.action, an InlineActionSchema) never enters that list, so no
cross-reference check ever visited one. A dangling type: 'modal' target built clean,
shipped, and failed only when a user clicked it.

This walks page/region/component trees for inline actions and subjects them to the same
checks as registered ones — same rule, same message tail, one more traversal. The flow
arm rides the same traversal.

Probe — the card's five stacks, before and after

Re-measured on main first (line numbers had drifted; the walk was re-anchored by
content). Rows F/G add the flow half the card names. Same object (probe_task) and page
(probe_home) in every stack; one defineStack call each.

# shape before (main) after (this PR)
A registered modal → object REJECTED REJECTED (unchanged)
B registered modal → page ACCEPTED ACCEPTED (unchanged)
C registered modal → nothing REJECTED REJECTED (unchanged)
D inline modal → object ACCEPTED REJECTED ← A/D split closed
E inline modal → nothing ACCEPTED REJECTED ← the card's defect
F registered flow → nothing REJECTED REJECTED (unchanged)
G inline flow → nothing ACCEPTED REJECTED ← the flow half

Rows added while building the traversal, all measured:

# shape after
H inline modal → declared page ACCEPTED (the legitimate shape survives)
I inline nested in a container's children REJECTED, path reported
J inline under slots rather than regions REJECTED, path reported
K inline anonymous (no name) REJECTED, identified by path
L inline legacy to spelling REJECTED (canonicalized by the schema)
M inline form → object edit view ACCEPTED (not a modal target)
N inline url / api / script / navigation ACCEPTED

Before-state excerpt, on main:

A registered modal -> object :  REJECTED -> Action 'probe_new_task' references page 'probe_task' (via modal target) which is not defined in pages.
C registered modal -> nothing:  REJECTED -> Action 'probe_new_task' references page 'probe_nowhere' (via modal target) which is not defined in pages.
D inline     modal -> object :  ACCEPTED
E inline     modal -> nothing:  ACCEPTED
G inline     flow  -> nothing:  ACCEPTED

After:

D inline modal -> object : REJECTED -> Inline action 'probe_new_task' on page 'probe_home' (regions.0.components.0) references page 'probe_task' (via modal target) which is not defined in pages.
E inline modal -> nothing: REJECTED -> Inline action 'probe_new_task' on page 'probe_home' (regions.0.components.0) references page 'probe_nowhere' (via modal target) which is not defined in pages.
G inline flow  -> nothing: REJECTED -> Inline action 'probe_run' on page 'probe_home' (regions.0.components.0) references flow 'probe_nowhere' which is not defined in flows.

Contract anchor — #6739's ruling fixes the modal arm

Row D is the A/D split, and its verdict is not this card's to invent. The maintainer ruled
it on #6739 on 2026-08-09 (comment),
quoted verbatim:

Maintainer ruling (2026-08-09): A — a type: 'modal' target names a PAGE, only. pm:queue.

The spec TSDoc, published docs, and stack.zod.ts cross-reference walk are the contract; objectui's page-then-object resolution is consumer leniency (self-labelled Back-compat) and enters retirement.

The same ruling sequenced this card explicitly:

#6889 (inline actions bypass cross-reference validation) stays Blocked-by this card until the showcase fix merges, then proceeds — its check would otherwise flag the current corpus. The dev's row-E finding makes it the more important card of the pair: inline is the shape AI authoring emits most readily.

The showcase fix merged as PR #7237, so the corpus is clean and the card proceeds. Because
the ruling settled the contract, the inline modal arm mirrors the registered rule
exactly
rather than also accepting an object name — no residual D/A question is escalated.

objectName has no inline counterpart to check: InlineActionSchema does not pick that
key, so an inline action cannot bind an object by name.

Corpus census — 0 shipped stacks newly refuse

Acceptance-face narrowing, so every defineStack corpus was censused — behaviourally, by
building it, not by grep alone:

corpus result
examples/app-showcase (16 files / 164 tests) pass — its one inline action is type: 'form' since #6739
examples/app-crm (2 / 27), examples/app-todo (3 / 105) pass
packages/qa/dogfood (86 / 534, 17 defineStack sites) pass
packages/spec (363 / 9488) pass
packages/lint (70 / 1852), packages/cli (107 / 1155) pass
packages/runtime (119 / 1870) pass
cloud tenant pages (5 inline buttons) unaffected — all type: 'url'

The only inline action in the reference corpus is the showcase home CTA
(examples/app-showcase/src/ui/pages/index.ts), type: 'form' + target: 'showcase_task.edit'. Neither form nor url is cross-referenced, so zero legitimate
shipped stacks break. A vacuity guard pins that shape in the new test file, so the census
cannot go stale silently.

Reverse verification — predicted in writing, then run

Predicted before running: removing the traversal must turn exactly the rejection-class
tests red — they assert a refusal old code never produces, so they fail with [] — while
every accept-class test stays green. The direction cannot invert here, because the change
only adds refusals; there is no shape that was rejected before and is accepted now.

Measured with git checkout origin/main -- packages/spec/src/stack.zod.ts (never
git stash — shared stack):

Test Files  1 failed (1)
     Tests  10 failed | 10 passed (20)

The 10 failures are exactly the 10 rejection-class cases enumerated in the prediction, and
the 10 accept-class cases stayed green. (One honest correction: the written prediction
summarised the set as "9 red" while the enumerated list held 10 — the set was predicted
exactly, the arithmetic summary was off by one.) Restoring the file returns 20/20 green.

Tests

packages/spec/src/stack-inline-action-crossref.test.ts — 20 cases. Rejection cases pin
full message text with toEqual, not toThrow: a bare throw assertion cannot separate
"refused for the right reason" from "refused because the fixture is broken", and each
rejection fixture differs from an accepted twin by exactly one string. Covered: both target
arms, the A/D-split parity table, the flow size gate, container nesting, slots, anonymous
actions, the legacy to spelling, a node InlineActionSchema cannot parse, multiple
offenders on one page, and the five action types that must stay untouched.

Implementation note

Candidates are normalized by parsing with InlineActionSchema rather than read
key-by-key: that schema's preprocess is what canonicalizes the legacy to spelling onto
target, and page-component properties are z.record(z.string(), z.unknown()), so a raw
node has not been through it. Reading action.target ?? action.to in the validator would be
a second, hand-mirrored copy of the producer's normalization — the consumer-side leniency
Prime Directive #12 rejects. When that parse fails the node is still checked from its raw
type/target strings, so a dangling reference cannot hide behind an unrelated defect;
the legacy spelling is deliberately not read there, since canonicalization stays the
schema's job.

The traversal recurses into any array under properties whose entries are component-shaped
(an object with a string type), which reaches every container spelling — children, a
card's footer — without this walk enumerating them.

Gates

pnpm lint (ESLint) exit 0 · check:nul-bytes OK (6741 files) · check:stack-collection-maps
· check:spec-parsed-alias · check:doc-authoring · check:docs-audit-scope ·
check:adr-anchors · check:quick-reference-counts · check:role-word ·
check:changeset-gate-self-tests · check:release-notes · check:engine-double-contract ·
check:error-code-casing · check:route-envelope — all OK. pnpm --filter @objectstack/spec typecheck clean.

No gate demanded a docs or ADR edit; content/docs/ and docs/adr/** are untouched.
Changeset: minor on @objectstack/spec, matching the precedent for extending this same
cross-reference walk to a new surface (nav-runaction-declared-contract.md, the #4848
deep-link check).


Generated by Claude Code

…-element actions (#6889)

`validateCrossReferences` iterated `config.actions` only, so an action authored
inline on a page element (`element:button` -> `properties.action`) was never
cross-referenced. Measured on main: a dangling inline `type: 'modal'` target
built clean and failed only when a user clicked, while the identical target on a
registered action was a build error.

Page `regions[].components[]`, `slots.*` and nested container children are now
walked, and every inline action found gets the SAME two target checks as a
registered one -- same rule, same message tail, same size gates. The `flow` arm
rides the same traversal.

The modal arm mirrors the registered rule rather than also accepting an object
name, per the maintainer ruling on #6739 (2026-08-09): a `type: 'modal'` target
names a PAGE, only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiRUoQkTSBBmpyXBY3cVn2
@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 10, 2026 9:07am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/permissions/system-context.mdx (via packages/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

7 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 31375385610 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Build Docs — 失败步骤: Install dependencies(日志不可读,点进 job 看)
  • Dogfood Verify CLI — 失败步骤: Install dependencies(日志不可读,点进 job 看)
  • Build Core — 失败步骤: Install dependencies(日志不可读,点进 job 看)
  • Temporal Conformance (live PG + MySQL) — 失败步骤: Install dependencies(日志不可读,点进 job 看)

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 8 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Merged via the queue into main with commit 09fe58d Aug 10, 2026
27 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-6889-inline-action-crossref branch August 10, 2026 10:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inline (page-element) actions bypass defineStack's action cross-reference validation entirely — a dangling type: 'modal' target builds clean

2 participants